package cn.edu.sjtu.process_management.service;

import cn.edu.sjtu.process_management.constant.HistoryStatus;
import cn.edu.sjtu.process_management.constant.RespStatus;
import cn.edu.sjtu.process_management.constant.TaskStatus;
import cn.edu.sjtu.process_management.dto.HistoryDTO;
import cn.edu.sjtu.process_management.dto.HistoryStatusDTO;
import cn.edu.sjtu.process_management.dto.ArgumentDTO;
import cn.edu.sjtu.process_management.dto.TaskDataDTO;
import cn.edu.sjtu.process_management.exception.model.ServiceException;
import cn.edu.sjtu.process_management.model.History;
import cn.edu.sjtu.process_management.model.Log;
import cn.edu.sjtu.process_management.model.Process;
import cn.edu.sjtu.process_management.repository.HistoryRepository;
import cn.edu.sjtu.process_management.repository.ProcessRepository;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author loumoon
 * @date 2019-11-01 13:37
 */
@Service
@Slf4j
public class HistoryService {
    @Autowired
    private HistoryRepository historyRepository;
    @Autowired
    private ProcessRepository processRepository;
    @Autowired
    private Mapper mapper;
    @Value("${detect.task.data.executing.num}")
    private Integer taskDataExecutingNum;
    @Value("${detect.task.data.done.num}")
    private Integer taskDataDoneNum;



    /*创建流程运行记录*/
    public History createHistory(Process process, String bpmnInstance) {
        History history = History.builder()
                .process(process.getId())
                .start(new Date())
                .bpmnInstance(bpmnInstance)
                .status(HistoryStatus.RUNNING.ordinal())
                .logs(new ArrayList<>())
                .build();
        historyRepository.save(history);
        return history;
    }

    /*查找所有流程运行记录*/
    public Map<Integer, List<HistoryDTO>> findAllHistories() {
        List<History> histories = historyRepository.findAll();
        List<HistoryDTO> historyDTOs = histories.stream()
                .map(history -> mapper.map(history, HistoryDTO.class)).collect(Collectors.toList());

        for (HistoryDTO historyDTO : historyDTOs) {
            Optional<Process> process = processRepository.findById(historyDTO.getProcess());
            if (!process.isPresent()) {
                log.error("No process matches the history with processId " + historyDTO.getProcess());
                throw new ServiceException(RespStatus.NON_EXIST_PROCESS, this.getClass().getName());
            }

            historyDTO.setProcessName(process.get().getName());
            historyDTO.setOwner(process.get().getOwner());

        }
        return historyDTOs.stream().collect(Collectors.groupingBy(HistoryDTO::getStatus));
    }

    /*查找用户创建的流程运行记录*/
    public Map<Integer, List<HistoryDTO>> findCreatedHistories(String owner) {
        List<History> histories = historyRepository.findAll();
        List<HistoryDTO> historyDTOs = histories.stream()
                .map(history -> mapper.map(history, HistoryDTO.class)).collect(Collectors.toList());

        Iterator<HistoryDTO> iter = historyDTOs.iterator();
        while (iter.hasNext()) {
            HistoryDTO historyDTO = iter.next();

            Optional<Process> process = processRepository.findById(historyDTO.getProcess());
            if (!process.isPresent()) {
                log.error("No process matches the history with processId " + historyDTO.getProcess());
                throw new ServiceException(RespStatus.NON_EXIST_PROCESS, this.getClass().getName());
            }

            if (!process.get().getOwner().equals(owner)) {
                iter.remove(); // 移除不属于该用户创建的historyDTO
            } else {
                historyDTO.setProcessName(process.get().getName());
                historyDTO.setOwner(owner);
            }

        }
        return historyDTOs.stream().collect(Collectors.groupingBy(HistoryDTO::getStatus));
    }

    /*查找流程运行状态*/
    public HistoryStatusDTO findHistoryStatus(String historyId) {
        History history = findHistory(historyId);

        HistoryStatusDTO historyStatusDTO = HistoryStatusDTO.builder()
                .status(history.getStatus())
                .build();

        Map<String, List<Log>> logs = history.getLogs().stream()
                .sorted(Comparator.comparing(Log::getTime).reversed()) // 日志按时间倒序
                .collect(Collectors.groupingBy(Log::getTask)); // 日志按照task分组

        if (logs.size() > 0) {
            List<Map> taskStatus = new ArrayList<>();
            for (Map.Entry<String, List<Log>> entry : logs.entrySet()) {
                Map<String, Integer> status = new HashMap<>();
                if (entry.getValue().get(0).getStatus() == null) { // 该task最近的log是启动log
                    status.put(entry.getKey(), TaskStatus.PENDING.ordinal());
                } else { // 该task最近的log是状态log
                    status.put(entry.getKey(), entry.getValue().get(0).getStatus());
                }
                taskStatus.add(status);
            }
            historyStatusDTO.setTaskStatus(taskStatus); // 所有已打日志的task的状态
        }
        return historyStatusDTO;
    }

    public HistoryStatusDTO findHistoryStatus(String historyId, String owner) {
        historyAccessCheck(historyId, owner);
        return findHistoryStatus(historyId);
    }

    /*查找流程运行过程中某task的返回数据*/
    public TaskDataDTO findHistoryTask(String historyId, String taskId) {
        History history = findHistory(historyId);
        List<Log> logs = history.getLogs().stream()
                .filter(log -> log.getTask().equals(taskId)) // 过滤出属于task的日志
                .sorted(Comparator.comparing(Log::getTime).reversed()) // 根据日志时间倒序
                .collect(Collectors.toList());

        List<Log> statusLogs = new ArrayList<>(logs);
        statusLogs.removeIf((Log log) -> log.getStatus() == null); // 获取状态log

        // 如果不存在状态log，task可能是在队列中，也可能尚未启动
        if (statusLogs.size() == 0) {
            logs.removeIf((Log log) -> log.getStatus() != null); // 获取启动log
            // task尚未启动
            if (logs.size() == 0) {
                return TaskDataDTO.builder()
                        .instance(null)
                        .taskStatus(TaskStatus.PENDING.ordinal())
                        .arguments(new ArrayList<>())
                        .build();
            }
            // task已启动，在队列中
            return TaskDataDTO.builder()
                    .instance(logs.get(0).getInstance())
                    .taskStatus(TaskStatus.PENDING.ordinal())
                    .arguments(new ArrayList<>())
                    .build();
        }

        // 存在状态log
        Log currentLog = statusLogs.get(0); // 当前日志
        TaskDataDTO taskDataDTO = TaskDataDTO.builder()
                .taskStatus(currentLog.getStatus())
                .instance(currentLog.getInstance()).build();

        // 如果是人工task的状态log
        if (currentLog.getResponseBody() == null) {
            taskDataDTO.setArguments(new ArrayList<>());
            return taskDataDTO;
        }

        List<Log> recentLogs; // 距离当前最近的若干组日志
        switch(currentLog.getStatus()) {
            case 1: // task在执行中
                recentLogs = statusLogs.stream()
                        .limit(taskDataExecutingNum)
                        .collect(Collectors.toList());
                break;
            case 2: // task执行完成or执行异常
                default:
                    recentLogs = statusLogs.stream()
                            .limit(taskDataDoneNum)
                            .collect(Collectors.toList());
                    break;
        }

        Map<String, ArgumentDTO> argumentDTOs = new HashMap<>();
        for (int i = 0; i < recentLogs.size(); ++i) {
            Map<String, Double> data = JSON.parseObject(recentLogs.get(i).getResponseBody(), Map.class); // 取出task的返回数据
            for (Map.Entry<String, Double> entry : data.entrySet()) {
                if (!argumentDTOs.containsKey(entry.getKey())) {
                    argumentDTOs.put(entry.getKey(), ArgumentDTO.builder().name(entry.getKey()).item(new ArrayList<>()).build());
                }
                Map map = new HashMap();
                map.put("timeStamp", recentLogs.get(i).getTime());
                map.put("value", entry.getValue());
                argumentDTOs.get(entry.getKey()).getItem().add(map);
            }
        }

        List<ArgumentDTO> argumentDTOList = new ArrayList<>(argumentDTOs.values());
        for (int i = 0; i < argumentDTOList.size(); ++i) { // 参数的展示图像的种类，奇奇怪怪的，这块很有问题
            if (i < argumentDTOList.size() / 2) {
                argumentDTOList.get(i).setType("LineChart");
            } else {
                argumentDTOList.get(i).setType("GaugeChart");
            }
        }

        taskDataDTO.setArguments(argumentDTOList);

        return taskDataDTO;
    }

    public TaskDataDTO findHistoryTask(String historyId, String taskId, String owner) {
        historyAccessCheck(historyId, owner);
        return findHistoryTask(historyId, taskId);
    }

    /*检查流程人员对某流程运行记录的访问权限*/
    private void historyAccessCheck(String historyId, String owner) {
        History history = findHistory(historyId);

        Optional<Process> process = processRepository.findById(history.getProcess());
        if (!process.isPresent()) { // 找不到匹配该运行记录的流程
            log.error("No process matches the history with processId " + history.getProcess());
            throw new ServiceException(RespStatus.NON_EXIST_PROCESS, this.getClass().getName());
        }

        if (!process.get().getOwner().equals(owner)) { // 非创建者无权访问该流程运行记录
            throw new ServiceException(RespStatus.NON_AUTHORITY_TO_HISTORY, this.getClass().getName());
        }
    }

    /*查找流程运行记录*/
    private History findHistory(String historyId) {
        Optional<History> history = historyRepository.findById(historyId);
        if (!history.isPresent()) { // 不存在的流程运行记录
            throw new ServiceException(RespStatus.NON_EXIST_HISTORY, this.getClass().getName());
        }
        return history.get();
    }
}
